
package tsp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class TSP_Data {

    private final static boolean DEBUG = true;

    private int nbSommets;
    private int alpha;
    private ArrayList<Double> x;
    private ArrayList<Double> y;

    private double[][] distances;
    private double[][] coutsCycle;
    private double[][] coutsAffectation;

    /**
     * Construit un objet qui initialise et remplit les matrices nécessaires.
     * @param filename Le chemin relatif vers le fichier test
     * @param alpha La valeur arbitraire saisie par l'utilisateur
     * @throws Exception En cas d'alpha invalide.
     */
    public TSP_Data(String filename, int alpha) throws Exception {

        /* Initialisation des attributs de classe */
        setAlpha(alpha);

        /* Initialisation x et y */
        x = new ArrayList<Double>();
        y = new ArrayList<Double>();

        /* Remplir x et y à partir de l'instance de test */
        if(!lireFichierTest(filename)) throw new Exception("x et y vides!");
        

        /* Initialisation des matrices requises par l'algorithme */
        initTableDistancesInitiales();

        /* Calcul des coûts cycle et affectation en fonction de alpha */
        initCouts();
    }

    /**
     * Lit une instance de test et remplit les listes de coordonnées
     * @param filename le nom du fichier test
     * @return true si x et y remplis, false sinon
     */
    private boolean lireFichierTest(String filename) {

        /* Lecture du fichier test */
        InputStream ips = TSP_Data.class.getResourceAsStream(filename);

        InputStreamReader ipsr = new InputStreamReader(ips);
        BufferedReader br = new BufferedReader(ipsr);

        String ligne;
        int cptLignes = 1;


        try {
            while ((ligne = br.readLine()).compareTo("EOF") != 0) {
                if (DEBUG && cptLignes <= 6 ) {
                    System.out.println(ligne);
                }

                if(cptLignes == 4) {
                    /* Récupérer la dimension */
                    String delimiteur = "[^0-9]"; //On ne garde que les chiffres
                    nbSommets = Integer.valueOf(ligne.substring(12, ligne.length()));
                }

                if(cptLignes > 6) {
                    StringTokenizer st = new StringTokenizer(ligne);

                    //Ajouter coordonnées trouvées dans x et dans y
                    while(st.hasMoreTokens()) {
                        st.nextToken();
                        x.add(Double.valueOf(st.nextToken()));
                        y.add(Double.valueOf(st.nextToken()));
                    }
                }

                cptLignes++;
            }

            br.close();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }

        return (!x.isEmpty() && !y.isEmpty());
    }

    /**
     * Initialiser la table des distances
     * @param x vecteur des val1
     * @param y vecteur des val2
     * @param nbSommets dimension des vecteurs
     */
    private void initTableDistancesInitiales() {
        distances = new double[nbSommets][nbSommets];

        //Mettre à zéro toutes les cases
        for (int i = 0; i < nbSommets; i++) {
            distances[i][i] = 0;
        }


        for (int i = 0; i < nbSommets - 1; i++) {
            for (int j = i + 1; j < nbSommets; j++) {
                distances[i][j] =  Math.sqrt(
                        Math.pow((x.get(i) - x.get(j)), 2)
                        + Math.pow((y.get(i) - y.get(j)), 2)
                        + 0.5);
                distances[j][i] = distances[i][j];
            }
        }

        x.clear(); //libérer mémoire
        y.clear();
    }


    /**
     * Initialise les matrices de couts et de distances à partir
     * de la table de distances initiale et en prenant en compte
     * alpha.
     */
    private void initCouts() {
        coutsCycle = new double[nbSommets][nbSommets];
        coutsAffectation  = new double[nbSommets][nbSommets];

        for (int i = 0; i < nbSommets; i++) {
            coutsCycle[i][i] = 0;
            coutsAffectation[i][i]  = 0;
        }

        for(int i=0; i<nbSommets-1; i++) {
            for(int j=i+1; j<nbSommets; j++) {
                coutsCycle[i][j] = Math.ceil(alpha * distances[i][j]);
                coutsAffectation[i][j]  = Math.ceil((10 - alpha) * distances[i][j]);

                coutsCycle[j][i] = coutsCycle[i][j];
                coutsAffectation[j][i]  = coutsAffectation[i][j];
            }
        }
    }

    /**
     * Renvoie le nombre de sommets utile à la dimension des matrices.
     * @return Le nombre de sommets utile à la dimension des matrices.
     */
    public int getNbSommets() {
        return nbSommets;
    }
    

    /**
     * Renvoie le facteur alpha choisi arbitrairement
     * @return Le facteur alpha choisi arbitrairement
     */
    public int getAlpha() {
        return alpha;
    }

    /**
     * Initialise alpha en vérifiant qu'il est dans l'intervalle autorisé
     * @param alpha le nombre entré par l'utilisateur
     * @throws Exception 
     */
    private void setAlpha(int n) throws Exception {
        if(n == 3 || n == 5
                || n == 7 || n == 9)
            this.alpha = n;
        else
            throw new IOException("Contrainte sur Alpha = 3 | 5 | 7 | 9");
    }

    /**
     * Renvoie la matrice des coûts.
     * @return La matrice des coûts.
     */
    public double[][] getCoutsCycle() {
        return coutsCycle;
    }

    /**
     * Renvoie la matrice des distances.
     * @return La matrice des distances.
     */
    public double[][] getCoutsAffectation() {
        return coutsAffectation;
    }


    /**
     * Affiche une chaine contenant la matrice de distances initiales
     */
    public void afficherMatriceDistances() {
        String str="";

        for(double[] ligne : distances) {
            str += "[";
            for(double cell : ligne) {
                str += cell+", ";
            }
           str +=  "],\n";
        }

        System.out.println(str);
    }

    /**
     * Affiche la matrice des couts des affectations calculés
     */
    public void afficherCoutsAffectation() {
        String str="\n===COUTS AFFECTATION===\n";

        for(double[] ligne : this.coutsAffectation) {
            str += "[";
            for(double cell : ligne) {
                str += cell+"  ";
                if(cell < 100) str += " ";
                if(cell < 10)  str += " ";
            }
           str +=  "]\n";
        }

        System.out.println(str);
    }

     /**
     * Affiche la matrice des couts du cycle calculés
     */
    public void afficherCoutsCycle() {
        String str="\n===COUTS CYCLE===\n";

        for(double[] ligne : this.coutsCycle) {
            str += "[";
            for(double cell : ligne) {
                str += cell+"  ";
                if(cell < 100) str += " ";
                if(cell < 10)  str += " ";
            }
           str +=  "]\n";
        }

        System.out.println(str);
    }
}
